Like XNA, Silverlight also supports two different programming interfaces for working with multi-touch,
which can be most easily categorized as low-level and high-level. The
low-level interface is based around the static Touch.FrameReported event, which is very similar to the XNA TouchPanel except that it’s an event and it doesn’t include gestures.
The high-level interface consists of three events defined by the UIElement class: ManipulationStarted, ManipulationDelta, and ManipulationCompleted. The Manipulation events, as they’re collectively called, consolidate the interaction of multiple fingers into movement and scaling factors.
The core of the low-level touch interface in Silverlight is a class called TouchPoint, an instance of which represents a particular finger touching the screen. TouchPoint has four get-only properties:
Action of type TouchAction, an enumeration with members Down, Move, and Up.
Position of type Point, relative to the upper-left corner of a particular element. Let’s call this element the reference element.
Size of type Size.
This is supposed to represent the touch area (and, hence, finger
pressure, more or less) but Windows Phone 7 doesn’t return useful
values.
TouchDevice of type TouchDevice.
The TouchDevice object has two get-only properties:
Id of type int, used to distinguish between fingers. A particular finger is associated with a unique Id for all events from Down through Up.
DirectlyOver of type UIElement, the topmost element underneath the finger.
As you can see, the Silverlight TouchPoint and TouchDevice objects give you mostly the same information as the XNA TouchLocation object, but the DirectlyOverproperty of TouchDevice is often very useful for determining what element the user is touching.
To use the low-level touch interface, you install a handler for the static Touch.FrameReported event:
Touch.FrameReported += OnTouchFrameReported;
The OnTouchFrameReported method looks like this:
void OnTouchFrameReported(object sender, TouchFrameEventArgs args)
{
. . .
}
The event handler gets all touch events throughout your application. The TouchFrameEventArgs object has a TimeStamp property of type int, plus three methods:
GetTouchPoints(refElement) returns a TouchPointCollection
GetPrimaryTouchPoint(refElement) returns one TouchPoint
SuspendMousePromotionUntilTouchUp()
In the general case, you call GetTouchPoints, passing to it a reference element. The TouchPoint objects in the returned collection have Position properties relative to that element. You can pass null to GetTouchPoints to get Position properties relative to the upper-left corner of the application.
The reference element and the DirectlyOver element have no relationship to each other. The event always gets all touch activity for the entire program. Calling GetTouchPoints or GetPrimaryTouchPoints with a particular element does not limit the events to only those events involving that element. All that it does is cause the Position property to be calculated relative to that element. (For that reason, Position coordinates can easily be negative if the finger is to the left of or above the reference element.) The DirectlyOver element indicates the element under the finger.
A discussion of the second and third methods requires some background: The Touch.FrameReported event originated on Silverlight
for the desktop, where it is convenient for the mouse logic of existing
controls to automatically use touch. For this reason, touch events are
“promoted” to mouse events.
But this promotion
only involves the “primary” touch point, which is the activity of the
first finger that touches the screen when no other fingers are touching
the screen. If you don’t want the activity of this finger to be promoted
to mouse events, the event handler usually begins like this:
void OnTouchFrameReported(object sender, TouchFrameEventArgs args)
{
TouchPoint primaryTouchPoint = args.GetPrimaryTouchPoint(null);
if (primaryTouchPoint != null && primaryTouchPoint.Action == TouchAction.Down)
{
args.SuspendMousePromotionUntilTouchUp();
}
. . .
}
The SuspendMousePromotionUntilTouchUp method can only be called when a finger first touches the screen when no other fingers are touching the screen.
On Windows Phone 7, such logic presents something of a quandary. As written, it basically wipes out all mouse
promotion throughout the application. If your phone application
incorporates Silverlight controls that were originally written for mouse
input but haven’t been upgraded to touch, you’re basically disabling
those controls.
Of course, you can also check the DirectlyOver
property to suspend mouse promotion selectively. But on the phone, no
elements should be processing mouse input except for those controls that
don’t process touch input! So it might make more sense to never suspend mouse promotion.
I’ll leave that
matter for your consideration and your older mouse-handling controls.
Meanwhile, the program I want to write is only interested in the primary
touch point when it has a TouchAction of Down, so I can use that same logic.
The SilverlightTouchHello project has a TextBlock in the XAML file:Silverlight Project:
Example 1. SilverlightTouchHello File: MainPage.xaml (excerpt)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <TextBlock Name="txtblk" Text="Hello, Windows Phone 7!" Padding="0 34" HorizontalAlignment="Center" VerticalAlignment="Center" /> </Grid>
|
Notice the Padding value. I know that the font displayed here has a FontSize property of 20 pixels, which actually translates into a TextBlock that is about 27 pixels tall. I also know that it’s recommended that touch
targets not be smaller than 9 millimeters. If the resolution of the
phone display is 264 DPI, then 9 millimeters is 94 pixels. (The
calculation is 9 millimeters divided by 25.4 millimeters to the inch,
times 264 pixels per inch.) The TextBlock is short by 67 pixels. So I set a Padding value that puts 34 more pixels on both the top and bottom (but not the sides).
I used Padding rather than Margin because Padding is space inside the TextBlock. The TextBlock actually becomes larger than the text size would imply. Margin is space outside the TextBlock. It’s not part of the TextBlock itself and is excluded for purposes of hit-testing.
Here’s the complete code-behind file. The constructor of MainPage installs the Touch.FrameReported event handler.
Example 2. Silverlight Project: SilverlightTouchHello File: MainPage.xaml.cs
using System; using System.Windows.Input; using System.Windows.Media; using Microsoft.Phone.Controls;
namespace SilverlightTouchHello { public partial class MainPage : PhoneApplicationPage { Random rand = new Random(); Brush originalBrush;
public MainPage() { InitializeComponent(); originalBrush = txtblk.Foreground; Touch.FrameReported += OnTouchFrameReported; }
void OnTouchFrameReported(object sender, TouchFrameEventArgs args) { TouchPoint primaryTouchPoint = args.GetPrimaryTouchPoint(null);
if (primaryTouchPoint != null && primaryTouchPoint.Action == TouchAction. Down) { if (primaryTouchPoint.TouchDevice.DirectlyOver == txtblk) { txtblk.Foreground = new SolidColorBrush( Color.FromArgb(255, (byte)rand.Next(256), (byte)rand.Next(256), (byte)rand.Next(256))); } else { txtblk.Foreground = originalBrush; } } } } }
|
The event handler is only interested in primary touch points with an Action of Down. If the DirectlyOver property is the element named txtblk, a random color is created. Unlike the Color structure in XNA, the Silverlight Color structure doesn’t have a constructor to set a color from red, green, and blue values, but it does have a static FromArgb method that creates a Color
object based on alpha, red, green, and blue values, where alpha is
opacity. Set the alpha channel to 255 to get an opaque color. Although
it’s not obvious at all in the XAML files, the Foreground property is actually of type Brush, an abstract class from which SolidColorBrush descends.
If DirectlyOver is not txtblk,
then the program doesn’t change the color to white, because that
wouldn’t work if the user chose a color theme of black text on a white
background. Instead, it sets the Foreground property to the brush originally set on the TextBlock. This is obtained in the constructor.